﻿using System;
using System.Data;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint.Utilities;

namespace SP2010InPlaceRecordsViewer
{
    class InPlaceRecordsViewerTimerJob : SPJobDefinition
    {
        #region Constants

        private const string EVENT_LOG_SOURCE = "SharePoint 2010 In Place Records Viewer";
        private const string RECORDS_LIST_TITLE = "In Place Records";
        private const string RECORDS_LIST_NAME = "InPlaceRecords";
        private const string RECORDS_LIST_DESCRIPTION = "This list will show the declared records across your site collection";
        private const string RECORDS_LIST_URL = "Lists/InPlaceRecords";
        private const string RECORDS_LIST_FEATURE_ID = "a6a76c0d-c406-4bca-936e-aff064da93fe";
        private const string RECORDS_LIST_DOC_TEMPLATE_ID = "100";

        private const int RECORDS_LIST_TEMPLATE_ID = 10222;

        #endregion

        #region Constructor(s)

        public InPlaceRecordsViewerTimerJob()
        {
        }

        public InPlaceRecordsViewerTimerJob(string jobName, SPService service, SPServer server, SPJobLockType targetType) : base(jobName, service, server, targetType)
        {
            Title = jobName;
        }

        public InPlaceRecordsViewerTimerJob(string jobName, SPWebApplication webApplication)
            : base(jobName, webApplication, null, SPJobLockType.ContentDatabase)
        {
            Title = jobName;
        }

        #endregion

        #region Overrides

        #region Execute

        public override void Execute(Guid contentDbId)
        {
            var webApplication = Parent as SPWebApplication;

            if (webApplication != null)
            {
                var contentDb = webApplication.ContentDatabases[contentDbId];

                foreach (SPSite siteCollection in contentDb.Sites)
                {
                    using (var site = new SPSite(siteCollection.RootWeb.Url))
                    {
                        using (var rootWeb = site.OpenWeb())
                        {
                            var recordsViewerList = rootWeb.Lists.TryGetList(RECORDS_LIST_TITLE);

                            #region Recreate the Previous List

                            if (recordsViewerList != null)
                            {
                                recordsViewerList.Delete();
                                rootWeb.Update();

                                var feature = site.Features.SingleOrDefault(sf => sf.Definition.DisplayName.Equals(RECORDS_LIST_NAME));

                                if (feature == null)
                                {
                                    siteCollection.Dispose();
                                    continue;
                                }

                                var newRecordsViewerListGuid = rootWeb.Lists.Add(RECORDS_LIST_TITLE,
                                                                                  RECORDS_LIST_DESCRIPTION,
                                                                                  RECORDS_LIST_URL,
                                                                                  RECORDS_LIST_FEATURE_ID,
                                                                                  RECORDS_LIST_TEMPLATE_ID,
                                                                                  RECORDS_LIST_DOC_TEMPLATE_ID,
                                                                                  SPListTemplate.QuickLaunchOptions.Off);
                                rootWeb.Update();

                                recordsViewerList = rootWeb.Lists[newRecordsViewerListGuid];

                                recordsViewerList.Hidden = true;
                                recordsViewerList.EnableVersioning = false;
                                recordsViewerList.AllowEveryoneViewItems = false;
                                recordsViewerList.AllowDeletion = true;
                                recordsViewerList.EnableAttachments = false;
                                recordsViewerList.EnableFolderCreation = false;
                                recordsViewerList.IsApplicationList = true;
                                recordsViewerList.NoCrawl = true;
                                recordsViewerList.RootWebOnly = true;

                                recordsViewerList.BreakRoleInheritance(false);

                                foreach (SPRoleAssignment roleAssignment in recordsViewerList.RoleAssignments)
                                {
                                    roleAssignment.RoleDefinitionBindings.RemoveAll();
                                    roleAssignment.Update();
                                }

                                recordsViewerList.Update();
                            }
                            #endregion

                            recordsViewerList = rootWeb.Lists.TryGetList(RECORDS_LIST_TITLE);

                            var dt = new DataTable();

                            if (recordsViewerList != null)
                            {
                                #region Query the entire site collection for all types list templates to retrieve the records

                                foreach (SPListTemplate listTemplate in rootWeb.ListTemplates)
                                {
                                    var query = new SPSiteDataQuery();

                                    query.Lists = "<Lists ServerTemplate='" + listTemplate.Type_Client.ToString(CultureInfo.InvariantCulture) + "' />";

                                    query.ViewFields = string.Format("<FieldRef ID='{0}' />", "Title") +
                                                       string.Format("<ProjectProperty Name='{0}' />", "Title") +
                                                       string.Format("<ListProperty Name='{0}' />", "Title") +
                                                       string.Format("<FieldRef Name='{0}' Nullable='TRUE' />", "_vti_ItemHoldRecordStatus") +  //Hold 4353 Record 273
                                                       string.Format("<FieldRef Name='{0}' Nullable='TRUE' />", "EncodedAbsUrl") +
                                                       string.Format("<FieldRef Name='{0}' Nullable='TRUE' />", "FileDirRef") +
                                                       string.Format("<FieldRef Name='{0}' Nullable='TRUE' />", "FileLeafRef") +
                                                       string.Format("<FieldRef Name='{0}' Nullable='TRUE' />", "_vti_ItemDeclaredRecord") +
                                                       string.Format("<FieldRef Name='{0}' Nullable='TRUE' />", "FileSizeDisplay") +
                                                       string.Format("<FieldRef Name='{0}' Nullable='TRUE' />", "ContentType") +
                                                       string.Format("<FieldRef Name='{0}' Nullable='TRUE' />", "UniqueId") +
                                                       string.Format("<FieldRef Name='{0}' Nullable='TRUE' />", "ID");

                                    //query.Query = "<Where><IsNotNull>" +
                                    //              string.Format("<FieldRef Name='{0}' Nullable='TRUE' />", "_vti_ItemDeclaredRecord") +
                                    //              "</IsNotNull></Where>";

                                    query.Query = "<Where><Or>" +
                                                  string.Format("<Eq><FieldRef Name='{0}' Nullable='TRUE' />", "_vti_ItemHoldRecordStatus") +
                                                  string.Format("<Value Type='Number'>{0}</Value></Eq>", "4353") +
                                                  string.Format("<Eq><FieldRef Name='{0}' Nullable='TRUE' />", "_vti_ItemHoldRecordStatus") +
                                                  string.Format("<Value Type='Number'>{0}</Value></Eq>", "273") +
                                                  "</Or></Where>";

                                    query.Webs = "<Webs Scope='SiteCollection' />";

                                    DataTable newDt = rootWeb.GetSiteData(query);

                                    dt.Merge(newDt, true, MissingSchemaAction.Add);
                                }

                                #endregion
                            }

                            #region Create a new item in the InPlaceRecords list  for each discovered record

                            foreach (DataRow row in dt.Rows)
                            {
                                var absUrl = row["EncodedAbsUrl"] as string ?? string.Empty;
                                var pathUrl = row["FileDirRef"] as string ?? string.Empty;
                                var id = row["ID"] as string;
                                var fileName = row["FileLeafRef"] as string ?? string.Empty;
                                var title = row["Title"] as string ?? string.Empty;
                                var fileSize = row["FileSizeDisplay"] as string ?? string.Empty;
                                var contentType = row["ContentType"] as string ?? string.Empty;
                                var siteName = row["ProjectProperty.Title"] as string ?? string.Empty;
                                var siteGuid = row["WebId"] as string ?? string.Empty;
                                var listGuid = row["ListId"] as string ?? string.Empty;
                                var itemGuid = row["UniqueId"] as string ?? string.Empty;
                                var listName = row["ListProperty.Title"] as string ?? string.Empty;
                                var recordState = row["_vti_ItemHoldRecordStatus"] as string;
                                DateTime? declaredDate = (!string.IsNullOrEmpty(row["_vti_ItemDeclaredRecord"] as string) ? Convert.ToDateTime(row["_vti_ItemDeclaredRecord"]) : (DateTime?)null);

                                if (!string.IsNullOrEmpty(recordState))
                                {
                                    if (recordState.Equals("273"))
                                        recordState = "Declared Record";
                                    else if (recordState.Equals("4353"))
                                        recordState = "Hold";
                                    else
                                        recordState = string.Empty;
                                }

                                if (!string.IsNullOrEmpty(fileSize))
                                {
                                    long fs = long.Parse(fileSize);
                                    fileSize = ToFileSize(fs);
                                }

                                if (!string.IsNullOrEmpty(fileName))
                                {
                                    fileName = fileName.Substring(fileName.IndexOf(";#", StringComparison.Ordinal) + 2);

                                    if (fileName.IndexOf("_.000", StringComparison.Ordinal) > -1)
                                        fileName = string.Empty;
                                }

                                if (!string.IsNullOrEmpty(itemGuid))
                                    itemGuid = itemGuid.Substring(itemGuid.IndexOf(";#", StringComparison.Ordinal) + 2);

                                string recordLibraryUrl = string.Format("{0}{1}", absUrl, pathUrl.Substring(pathUrl.IndexOf(";#", StringComparison.Ordinal) + 2));
                                string recordUrl = string.Format("{0}/{1}", recordLibraryUrl, (string.IsNullOrEmpty(fileName) ? "dispform.aspx?id=" + id : fileName));

                                var fuvRecordUrl = new SPFieldUrlValue();
                                fuvRecordUrl.Description = string.IsNullOrEmpty(fileName) ? title : fileName;
                                fuvRecordUrl.Url = recordUrl;

                                var fuvRecordLibraryUrl = new SPFieldUrlValue();
                                fuvRecordLibraryUrl.Description = listName;

                                var fuvRecordFolderUrl = new SPFieldUrlValue();
                                fuvRecordFolderUrl.Url = SPUtility.GetUrlDirectory(recordUrl);

                                var fuvRecordSiteUrl = new SPFieldUrlValue();
                                fuvRecordSiteUrl.Description = siteName;

                                using (SPWeb web = site.AllWebs[new Guid(siteGuid)])
                                {
                                    fuvRecordSiteUrl.Url = web.Url;

                                    SPList recordList = web.Lists[new Guid(listGuid)];
                                    fuvRecordLibraryUrl.Url = SPUtility.GetFullUrl(site, recordList.DefaultViewUrl);

                                    SPListItem recordListItem = recordList.Items[new Guid(itemGuid)];

                                    SPFolder recordFolder = GetParentFolder(recordListItem, null);

                                    if (recordFolder != null && recordFolder.Exists)
                                        fuvRecordFolderUrl.Description = recordFolder.Name;
                                    else
                                        fuvRecordFolderUrl.Description = "Root";
                                }

                                if (recordsViewerList != null)
                                {
                                    SPListItem record = recordsViewerList.Items.Add();

                                    record["Title"] = title;
                                    record["Record Url"] = fuvRecordUrl;
                                    record["Record Library"] = fuvRecordLibraryUrl;
                                    record["Record Folder"] = fuvRecordFolderUrl;
                                    record["Record Site Url"] = fuvRecordSiteUrl;
                                    record["Record Content Type"] = contentType;
                                    record["Record Size"] = fileSize;
                                    record["Record State"] = recordState;

                                    if (declaredDate != null)
                                        record["Record Declared Date"] = declaredDate;

                                    record.Update();
                                }
                            }

                            #endregion

                        }
                    }

                    siteCollection.Dispose();
                }
            }
        }

        #endregion

        #region Description

        // Override built-in Description property by returning our custom description in the getter accessor
        public override string Description
        {
            get
            {
                return CustomDescription;
            }
        }

        #endregion

        #endregion

        #region Private Properties

        #region CustomDescription

        //Custom Job Description
        public string CustomDescription
        {
            get;
            set;
        }

        #endregion

        #endregion

        #region Private Methods

        #region LogMessage

        public static void LogMessage(string message)
        {
            SPSecurity.RunWithElevatedPrivileges(delegate()
            {
                if (!EventLog.SourceExists(EVENT_LOG_SOURCE))
                {
                    EventLog.CreateEventSource(EVENT_LOG_SOURCE, "Application");
                }

                EventLog.WriteEntry(EVENT_LOG_SOURCE,
                                            message,
                                            EventLogEntryType.Error);
            });
        }

        #endregion

        #region ToFileSize

        public string ToFileSize(long source)
        {
            const int byteConversion = 1024;

            var bytes = Convert.ToDouble(source);

            if (bytes >= Math.Pow(byteConversion, 3)) //GB Range    
                return string.Concat(Math.Round(bytes / Math.Pow(byteConversion, 3), 2), " GB");
            
            if (bytes >= Math.Pow(byteConversion, 2)) //MB Range    
                return string.Concat(Math.Round(bytes / Math.Pow(byteConversion, 2), 2), " MB");
            
            if (bytes >= byteConversion) //KB Range    
                return string.Concat(Math.Round(bytes / byteConversion, 2), " KB");

            return string.Concat(bytes, " Bytes");
        }

        #endregion

        #region GetParentFolder

        private static SPFolder GetParentFolder(SPListItem itemToFind, SPFolder folder)
        {
            var query = new SPQuery {Folder = folder};

            var items = itemToFind.ParentList.GetItems(query);

            foreach (SPListItem item in items)
            {
                if (item.ID == itemToFind.ID)
                    return (folder);

                if (item.Folder != null)
                {
                    var resultFolder = GetParentFolder(itemToFind, item.Folder);

                    if (resultFolder != null)
                        return (resultFolder);
                }
            }

            return (null);
        }

        #endregion

        #endregion
    }
}